﻿// Copyright (c) Alexandre Mutel. All rights reserved.
// Licensed under the BSD-Clause 2 license.
// See license.txt file in the project root for full license information.

using System.Text;

namespace CppAst
{
    /// <summary>
    /// A C++ class, struct or union.
    /// </summary>
    [Serializable]
    public sealed class CppClass : CppTypeDeclaration, ICppMemberWithVisibility, ICppDeclarationContainer,
        ICppTemplateOwner
    {
        private CppClass _SpecializedTemplate;
        private CppClassKind _ClassKind;
        private CppVisibility _Visibility;
        private bool _hasImplement;
        private bool _IsAnonymous;
        private List<CppBaseType> _BaseTypes;
        private List<CppBaseType> _SpecialLizedBaseTypes = null;
        private CppContainerList<CppFunction> _Functions;
        private CppContainerList<CppFunction> _SepcialLizedFunctions;
        private CppContainerList<CppField> _Fields;
        private CppContainerList<CppField> _SpecializedFields;
        private CppContainerList<CppFunction> _Constructors;
        private CppContainerList<CppFunction> _SpecializedConstructors;
        private CppContainerList<CppFunction> _Destructors;
        private CppContainerList<CppFunction> _SpecializedDestructors;
        private CppContainerList<CppClass> _Classes;
        private CppContainerList<CppClass> _SpecializedClasses;
        private CppContainerList<CppEnum> _SpecializedEnums;
        private CppContainerList<CppEnum> _Enums;
        private bool _IsAbstract;
        private CppTemplateKind _TemplateKind;

        /// <summary>
        /// Creates a new instance.
        /// </summary>
        /// <param name="name">Name of this type.</param>
        public CppClass(string name) : base(CppTypeKind.StructOrClass)
        {
            Name = name ?? throw new ArgumentNullException(nameof(name));
            BaseTypes = new List<CppBaseType>();
            Fields = new CppContainerList<CppField>(this);
            Constructors = new CppContainerList<CppFunction>(this);
            Destructors = new CppContainerList<CppFunction>(this);
            Functions = new CppContainerList<CppFunction>(this);
            Enums = new CppContainerList<CppEnum>(this);
            Classes = new CppContainerList<CppClass>(this);
            Typedefs = new CppContainerList<CppTypedef>(this);
            TemplateParameters = new List<CppType>();
            Attributes = new List<CppAttribute>();
            TokenAttributes = new List<CppAttribute>();
        }

        /// <summary>
        /// Kind of the instance (`class` `struct` or `union`)
        /// </summary>
        public CppClassKind ClassKind
        {
            get
            {
                if (this.TemplateKind == CppTemplateKind.TemplateSpecializedClass)
                {
                    if (this.SpecializedTemplate != null)
                    {
                        return this.SpecializedTemplate.ClassKind;
                    }
                }

                return this._ClassKind;
            }

            set { this._ClassKind = value; }
        }

        public CppTemplateKind TemplateKind
        {
            get { return this._TemplateKind; }
            set { this._TemplateKind = value; }
        }

        /// <inheritdoc />
        public string Name { get; set; }


        public override string FullName
        {
            get
            {
                StringBuilder sb = new StringBuilder();
                string fullparent = FullParentName;
                var useName = Name;
                if (this.ClassKind == CppClassKind.Union)
                {
                    foreach (var cppField in this.Fields)
                    {
                        useName += "_" + cppField.Name;
                    }
                }

                if (string.IsNullOrEmpty(fullparent))
                {
                    sb.Append(useName);
                }
                else
                {
                    sb.Append($"{fullparent}::{useName}");
                }

                if (TemplateKind == CppTemplateKind.TemplateClass
                    || TemplateKind == CppTemplateKind.PartialTemplateClass)
                {
                    sb.Append('<');
                    for (int i = 0; i < TemplateParameters.Count; i++)
                    {
                        var tp = TemplateParameters[i];
                        if (i != 0)
                        {
                            sb.Append(", ");
                        }

                        sb.Append(tp.ToString());
                    }

                    sb.Append('>');
                }
                else if (TemplateKind == CppTemplateKind.TemplateSpecializedClass)
                {
                    sb.Append('<');
                    for (int i = 0; i < TemplateSpecializedArguments.Count; i++)
                    {
                        var ta = TemplateSpecializedArguments[i];
                        if (i != 0)
                        {
                            sb.Append(", ");
                        }

                        sb.Append(ta.ArgString);
                    }

                    sb.Append('>');
                }

                return sb.ToString();
            }
        }

        /// <inheritdoc />
        public CppVisibility Visibility
        {
            get
            {
                if (this.TemplateKind == CppTemplateKind.TemplateSpecializedClass)
                {
                    return this.SpecializedTemplate.Visibility;
                }

                return this._Visibility;
            }
            set { this._Visibility = value; }
        }

        /// <inheritdoc />
        public List<CppAttribute> Attributes { get; }

        [Obsolete("TokenAttributes is deprecated. please use system attributes and annotate attributes")]
        public List<CppAttribute> TokenAttributes { get; }

        public MetaAttributeMap MetaAttributes { get; private set; } = new MetaAttributeMap();

        /// <summary>
        /// Gets or sets a boolean indicating if this type is a definition. If <c>false</c> the type was only declared but is not defined.
        /// </summary>
        public bool HasImplement
        {
            get
            {
                if (this.TemplateKind == CppTemplateKind.TemplateSpecializedClass)
                {
                    return this.SpecializedTemplate.HasImplement;
                }

                return this._hasImplement;
            }
            set { this._hasImplement = value; }
        }

        /// <summary>
        /// Gets or sets a boolean indicating if this declaration is anonymous.
        /// </summary>
        public bool IsAnonymous
        {
            get
            {
                if (this.TemplateKind == CppTemplateKind.TemplateSpecializedClass)
                {
                    return this.SpecializedTemplate._IsAnonymous;
                }

                return this._IsAnonymous;
            }
            set { this._IsAnonymous = value; }
        }

        /// <summary>
        /// Get the base types of this type.
        /// </summary>
        public List<CppBaseType> BaseTypes
        {
            get
            {
                if (this.TemplateKind == CppTemplateKind.TemplateSpecializedClass)
                {
                    if (this._SpecialLizedBaseTypes != null)
                    {
                        return this._SpecialLizedBaseTypes;
                    }

                    this._SpecialLizedBaseTypes = new List<CppBaseType>();
                    foreach (var specializedTemplateBaseType in this.SpecializedTemplate.BaseTypes)
                    {
                        var newBaseType = new CppBaseType(GetReplacedType(specializedTemplateBaseType.Type));
                        this._SpecialLizedBaseTypes.Add(newBaseType);
                    }

                    return this._SpecialLizedBaseTypes;
                }

                return this._BaseTypes;
            }
            set { this._BaseTypes = value; }
        }

        /// <inheritdoc />
        public CppContainerList<CppField> Fields
        {
            get
            {
                if (this.TemplateKind == CppTemplateKind.TemplateSpecializedClass)
                {
                    if (this._SpecializedFields != null)
                    {
                        return this._SpecializedFields;
                    }

                    this._SpecializedFields = new CppContainerList<CppField>(this);
                    foreach (var specializedTemplateBaseType in this.SpecializedTemplate.Fields)
                    {
                        var replacedType = GetReplacedType(specializedTemplateBaseType.Type);
                        var newField = new CppField(replacedType, specializedTemplateBaseType.Name);
                        newField.Visibility = specializedTemplateBaseType.Visibility;
                        newField.StorageQualifier = specializedTemplateBaseType.StorageQualifier;
                        newField.IsBitField = specializedTemplateBaseType.IsBitField;
                        this._SpecializedFields.Add(newField);
                    }

                    return this._SpecializedFields;
                }

                return this._Fields;
            }
            set { this._Fields = value; }
        }

        /// <summary>
        /// Gets the constructors of this instance.
        /// </summary>
        public CppContainerList<CppFunction> Constructors
        {
            get
            {
                if (this.TemplateKind == CppTemplateKind.TemplateSpecializedClass)
                {
                    var sepcialLizedFunctions = this._SpecializedConstructors;

                    if (sepcialLizedFunctions != null)
                    {
                        return sepcialLizedFunctions;
                    }

                    this._SpecializedConstructors = new CppContainerList<CppFunction>(this);
                    sepcialLizedFunctions = this._SpecializedConstructors;
                    var specializedTemplateFunctions = this.SpecializedTemplate.Constructors;
                    foreach (var cppFunction in specializedTemplateFunctions)
                    {
                        var newFunction = cppFunction.SimpleCloneWithOutParaAndReturnType();
                        foreach (var cppFunctionParameter in cppFunction.Parameters)
                        {
                            var newParameter = new CppParameter(GetReplacedType(cppFunctionParameter.Type),
                                cppFunctionParameter.Name);
                            newFunction.Parameters.Add(newParameter);
                        }

                        newFunction.ReturnType = GetReplacedType(cppFunction.ReturnType);
                        sepcialLizedFunctions.Add(newFunction);
                    }

                    return sepcialLizedFunctions;
                }

                return this._Constructors;
            }
            set { this._Constructors = value; }
        }

        /// <summary>
        /// Gets the destructors of this instance.
        /// </summary>
        public CppContainerList<CppFunction> Destructors
        {
            get
            {
                if (this.TemplateKind == CppTemplateKind.TemplateSpecializedClass)
                {
                    var sepcialLizedFunctions = this._SpecializedDestructors;

                    if (sepcialLizedFunctions != null)
                    {
                        return sepcialLizedFunctions;
                    }

                    this._SpecializedDestructors = new CppContainerList<CppFunction>(this);
                    sepcialLizedFunctions = this._SpecializedDestructors;
                    var specializedTemplateFunctions = this.SpecializedTemplate.Destructors;
                    foreach (var cppFunction in specializedTemplateFunctions)
                    {
                        var newFunction = cppFunction.SimpleCloneWithOutParaAndReturnType();
                        foreach (var cppFunctionParameter in cppFunction.Parameters)
                        {
                            var newParameter = new CppParameter(GetReplacedType(cppFunctionParameter.Type),
                                cppFunctionParameter.Name);
                            newFunction.Parameters.Add(newParameter);
                        }

                        newFunction.ReturnType = GetReplacedType(cppFunction.ReturnType);
                        sepcialLizedFunctions.Add(newFunction);
                    }

                    return sepcialLizedFunctions;
                }

                return this._Destructors;
            }
            set { this._Destructors = value; }
        }

        /// <inheritdoc />
        public CppContainerList<CppFunction> Functions
        {
            get
            {
                if (this.TemplateKind == CppTemplateKind.TemplateSpecializedClass)
                {
                    var sepcialLizedFunctions = this._SepcialLizedFunctions;

                    if (sepcialLizedFunctions != null)
                    {
                        return sepcialLizedFunctions;
                    }

                    this._SepcialLizedFunctions = new CppContainerList<CppFunction>(this);
                    sepcialLizedFunctions = this._SepcialLizedFunctions;
                    var specializedTemplateFunctions = this.SpecializedTemplate.Functions;
                    foreach (var cppFunction in specializedTemplateFunctions)
                    {
                        var newFunction = cppFunction.SimpleCloneWithOutParaAndReturnType();
                        foreach (var cppFunctionParameter in cppFunction.Parameters)
                        {
                            var newParameter = new CppParameter(GetReplacedType(cppFunctionParameter.Type),
                                cppFunctionParameter.Name);
                            newFunction.Parameters.Add(newParameter);
                        }

                        newFunction.ReturnType = GetReplacedType(cppFunction.ReturnType);
                        sepcialLizedFunctions.Add(newFunction);
                    }

                    return sepcialLizedFunctions;
                }

                return this._Functions;
            }
            set { this._Functions = value; }
        }

        private CppComment _comment;

        public override CppComment Comment
        {
            get
            {
                if (this.TemplateKind == CppTemplateKind.PartialTemplateClass || this.TemplateKind == CppTemplateKind.TemplateSpecializedClass)
                {
                    if (this.SpecializedTemplate != null)
                    {
                        return this.SpecializedTemplate.Comment;
                    }
                }
                return _comment;
            }
            set { _comment = value; }
        }

        /// <inheritdoc />
        public CppContainerList<CppEnum> Enums
        {
            get
            {
                if (this.TemplateKind == CppTemplateKind.TemplateSpecializedClass)
                {
                    if (this._SpecializedEnums == null)
                    {
                        this._SpecializedEnums = new CppContainerList<CppEnum>(this);
                    }

                    return this._SpecializedEnums;
                }

                return this._Enums;
            }
            set { this._Enums = value; }
        }

        /// <inheritdoc />
        public CppContainerList<CppClass> Classes
        {
            get
            {
                if (this.TemplateKind == CppTemplateKind.TemplateSpecializedClass)
                {
                    if (this._SpecializedClasses != null)
                    {
                        return _SpecializedClasses;
                    }

                    this._SpecializedClasses = new CppContainerList<CppClass>(this);
                    var templateClasses = this.SpecializedTemplate.Classes;
                    foreach (var subClass in templateClasses)
                    {
                        if (subClass.TemplateKind == CppTemplateKind.NormalClass ||
                            subClass.TemplateKind == CppTemplateKind.TemplateClass)
                        {
                            continue;
                        }

                        var specializedSubClass = new CppClass(subClass.Name);
                        specializedSubClass.Comment = subClass.Comment;
                        specializedSubClass.TemplateKind = CppTemplateKind.TemplateSpecializedClass;
                        specializedSubClass.TemplateParameters.AddRange(subClass.TemplateParameters);
                        foreach (var templateSpecializedArgument in specializedSubClass.TemplateSpecializedArguments)
                        {
                            var newArgument = new CppTemplateArgument(templateSpecializedArgument.SourceParam,
                                this.GetReplacedType(templateSpecializedArgument.ArgAsType));
                            specializedSubClass.TemplateSpecializedArguments.Add(newArgument);
                        }

                        this._SpecializedClasses.Add(specializedSubClass);
                    }

                    return this._SpecializedClasses;
                }

                return this._Classes;
            }
            set { this._Classes = value; }
        }

        /// <inheritdoc />
        public CppContainerList<CppTypedef> Typedefs { get; }

        /// <inheritdoc />
        public List<CppType> TemplateParameters { get; }

        public List<CppTemplateArgument> TemplateSpecializedArguments { get; } = new List<CppTemplateArgument>();

        /// <summary>
        /// Gets the specialized class template of this instance.
        /// </summary>
        public CppClass SpecializedTemplate
        {
            get { return this._SpecializedTemplate; }
            set
            {
                this._SpecializedTemplate = value;
                if (value != null)
                {
                    this.ClassKind = value.ClassKind;
                }
            }
        }


        public bool IsEmbeded => Parent is CppClass;

        public bool IsAbstract
        {
            get
            {
                if (this.TemplateKind == CppTemplateKind.TemplateSpecializedClass)
                {
                    return this.SpecializedTemplate.IsAbstract;
                }

                return this._IsAbstract;
            }
            set { this._IsAbstract = value; }
        }


        private bool Equals(CppClass other)
        {
            if (!base.Equals(other))
            {
                return false;
            }

            if (!Equals(Parent, other.Parent))
            {
                return false;
            }

            if (!Name.Equals(other.Name))
            {
                return false;
            }

            if (this.Fields.Count != other.Fields.Count)
            {
                return false;
            }

            for (var fieldIndex = 0; fieldIndex < this.Fields.Count; fieldIndex++)
            {
                var thisField = this.Fields[fieldIndex];
                var otherField = other.Fields[fieldIndex];
                if (!thisField.Name.Equals(otherField.Name))
                {
                    return false;
                }

                if (!thisField.Type.Equals(otherField.Type))
                {
                    return false;
                }
            }

            if (this.TemplateKind != other.TemplateKind)
            {
                return false;
            }

            if (this.TemplateKind == CppTemplateKind.TemplateSpecializedClass)
            {
                for (var index = 0; index < this.TemplateSpecializedArguments.Count; index++)
                {
                    var arg1 = this.TemplateSpecializedArguments[index];
                    var arg2 = other.TemplateSpecializedArguments[index];
                    if (!Equals(arg1.ArgAsType, arg2.ArgAsType))
                    {
                        return false;
                    }

                    if (!Equals(arg1.ArgAsInteger, arg2.ArgAsInteger))
                    {
                        return false;
                    }

                    if (!Equals(arg1.ArgString, arg2.ArgString))
                    {
                        return false;
                    }

                    if (!Equals(arg1.ArgAsUnknown, arg2.ArgAsUnknown))
                    {
                        return false;
                    }
                }
            }

            return true;
        }

        /// <inheritdoc />
        public override int SizeOf { get; set; }

        /// <summary>
        /// Gets the alignment of this instance.
        /// </summary>
        public int AlignOf { get; set; }

        /// <inheritdoc />
        public override bool Equals(object obj)
        {
            return ReferenceEquals(this, obj) || obj is CppClass other && Equals(other);
        }

        /// <inheritdoc />
        public override int GetHashCode()
        {
            unchecked
            {
                int hashCode = base.GetHashCode();
                hashCode = (hashCode * 397) ^ (Parent != null ? Parent.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ Name.GetHashCode();
                foreach (var templateParameter in TemplateParameters)
                {
                    hashCode = (hashCode * 397) ^ templateParameter.GetHashCode();
                }

                foreach (var templateArgument in TemplateSpecializedArguments)
                {
                    hashCode = (hashCode * 397) ^ templateArgument.GetHashCode();
                }

                return hashCode;
            }
        }

        public override CppClass Clone()
        {
            return this;
        }

        /// <inheritdoc />
        public override CppType GetCanonicalType()
        {
            return this;
        }

        /// <inheritdoc />
        public override string ToString()
        {
            var builder = new StringBuilder();
            switch (ClassKind)
            {
                case CppClassKind.Class:
                    builder.Append("class ");
                    break;
                case CppClassKind.Struct:
                    builder.Append("struct ");
                    break;
                case CppClassKind.Union:
                    builder.Append("union ");
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            if (!string.IsNullOrEmpty(Name))
            {
                builder.Append(Name);
            }

            if (ClassKind == CppClassKind.Union)
            {
                foreach (var field in Fields)
                {
                    builder.AppendFormat("_{0}", field.Name);
                }
            }

            if (BaseTypes.Count > 0)
            {
                builder.Append(" : ");
                for (var i = 0; i < BaseTypes.Count; i++)
                {
                    var baseType = BaseTypes[i];
                    if (i > 0) builder.Append(", ");
                    builder.Append(baseType);
                }
            }

            //Add template arguments here
            if (TemplateKind != CppTemplateKind.NormalClass)
            {
                builder.Append("<");

                if (TemplateKind == CppTemplateKind.TemplateSpecializedClass)
                {
                    for (var i = 0; i < TemplateSpecializedArguments.Count; i++)
                    {
                        if (i > 0) builder.Append(", ");
                        builder.Append(TemplateSpecializedArguments[i].ToString());
                    }
                }
                else if (TemplateKind == CppTemplateKind.TemplateClass)
                {
                    for (var i = 0; i < TemplateParameters.Count; i++)
                    {
                        if (i > 0) builder.Append(", ");
                        builder.Append(TemplateParameters[i].ToString());
                    }
                }

                builder.Append(">");
            }

            builder.Append(" { ... }");
            return builder.ToString();
        }

        public override IEnumerable<ICppDeclaration> Children()
        {
            foreach (var item in CppContainerHelper.Children(this))
            {
                yield return item;
            }

            foreach (var item in Constructors)
            {
                yield return item;
            }

            foreach (var item in Destructors)
            {
                yield return item;
            }
        }

        public override string SourceFile
        {
            get
            {
                if (this.TemplateKind != CppTemplateKind.NormalClass)
                {
                    if (this.SpecializedTemplate != null)
                    {
                        return this.SpecializedTemplate.SourceFile;
                    }
                }

                if (!string.IsNullOrWhiteSpace(Span.Start.File))
                {
                    return Span.Start.File;
                }

                if (Parent is CppElement cppElement)
                {
                    return cppElement.SourceFile;
                }

                return Span.End.File;
            }
        }

        private CppType GetReplacedType(CppType sourceType)
        {
            if (sourceType is CppUnexposedType cppUnexposedType)
            {
                var replacedTypeArgument = this.TemplateSpecializedArguments.Find(argument =>
                    argument.SourceParam.FullName.Equals(cppUnexposedType.Name));
                if (replacedTypeArgument != null)
                {
                    return replacedTypeArgument.ArgAsType;
                }
            }

            if (sourceType is CppTypeWithElementType cppTypeWithElementType)
            {
                var cloned = cppTypeWithElementType.Clone() as CppTypeWithElementType;
                cloned.ElementType = GetReplacedType(cppTypeWithElementType.ElementType);
                return cloned;
            }

            if (sourceType is CppClass cppClass)
            {
                if (cppClass.TemplateKind == CppTemplateKind.NormalClass)
                {
                    return sourceType;
                }

                if (cppClass.TemplateKind == CppTemplateKind.TemplateClass ||
                    cppClass.TemplateKind == CppTemplateKind.PartialTemplateClass)
                {
                    var replacedTemplate = new CppClass(cppClass.Name);
                    replacedTemplate.Comment = cppClass.Comment;
                    replacedTemplate.Parent = cppClass.Parent;
                    if (cppClass.TemplateKind == CppTemplateKind.TemplateClass)
                    {
                        replacedTemplate.SpecializedTemplate = cppClass;
                    }
                    else
                    {
                        replacedTemplate.SpecializedTemplate = cppClass.SpecializedTemplate;
                    }

                    var notImplementCnt = 0;
                    for (var index = 0; index < this.TemplateSpecializedArguments.Count; index++)
                    {
                        var sourceTemplateArgument = this.TemplateSpecializedArguments[index];
                        if (sourceTemplateArgument.ArgKind == CppTemplateArgumentKind.AsType)
                        {
                            CppType useType = sourceTemplateArgument.ArgAsType;
                            if (useType != null)
                            {
                                if (useType is CppUnexposedType paraUnexposedType)
                                {
                                    var replacedTypeArgument = this.TemplateSpecializedArguments.Find(argument =>
                                        argument.SourceParam.FullName.Equals(paraUnexposedType.Name));
                                    if (replacedTypeArgument != null)
                                    {
                                        useType = replacedTypeArgument.ArgAsType;
                                    }
                                }

                                if (useType is CppUnexposedType)
                                {
                                    notImplementCnt++;
                                }

                                if (cppClass.TemplateSpecializedArguments.Count > 0)
                                {
                                    var preUsedype = cppClass.TemplateSpecializedArguments.Find(argument =>
                                    {
                                        return argument.SourceParam.FullName == sourceTemplateArgument.SourceParam.FullName;
                                    });
                                    if (preUsedype != null)
                                    {
                                        useType = this.GetReplacedType(preUsedype.ArgAsType);
                                    }
                                }

                                replacedTemplate.TemplateSpecializedArguments.Add(
                                    new CppTemplateArgument(sourceTemplateArgument.SourceParam, useType));
                            }
                        }
                        else if (sourceTemplateArgument.ArgKind == CppTemplateArgumentKind.AsInteger)
                        {
                            replacedTemplate.TemplateSpecializedArguments.Add(
                                new CppTemplateArgument(sourceTemplateArgument.SourceParam, sourceTemplateArgument.ArgAsInteger));
                        }
                        else if (sourceTemplateArgument.ArgKind == CppTemplateArgumentKind.AsInteger)
                        {
                            replacedTemplate.TemplateSpecializedArguments.Add(
                                new CppTemplateArgument(sourceTemplateArgument.SourceParam, sourceTemplateArgument.ArgString));
                        }
                    }


                    if (notImplementCnt == this.TemplateSpecializedArguments.Count)
                    {
                        replacedTemplate.TemplateKind = CppTemplateKind.TemplateClass;
                    }
                    else if (notImplementCnt == 0)
                    {
                        replacedTemplate.TemplateKind = CppTemplateKind.TemplateSpecializedClass;
                    }
                    else
                    {
                        replacedTemplate.TemplateKind = CppTemplateKind.PartialTemplateClass;
                    }

                    return replacedTemplate;
                }
            }

            return sourceType;
        }
    }
}